#include <rtthread.h>
#include <rtdevice.h>

#include "board.h"
#include "dvp.h"
#include "sysctl.h"

struct d_camif
{
    rt_camif_t ci;
};

static int sensor_irq(void *ctx)
{
    struct d_camif *dci = (struct d_camif*)ctx;

    if (dvp_get_interrupt(DVP_STS_FRAME_FINISH))
    { //frame end
        dvp_clear_interrupt(DVP_STS_FRAME_START | DVP_STS_FRAME_FINISH);
        rt_camif_complete(&dci->ci, 0);
    }
    else
    {
        if (dci->ci.active)
            dvp_start_convert();    //so we need deal img ontime, or skip one framebefore next
        dvp_clear_interrupt(DVP_STS_FRAME_START);
        //rt_kprintf("T%X", dci->ci.active);
    }

    return 0;
}

static int sensor_run(int enable)
{
    if (enable)
    {
        dvp_clear_interrupt(DVP_STS_FRAME_START | DVP_STS_FRAME_FINISH);
        plic_irq_enable(IRQN_DVP_INTERRUPT);
        dvp_config_interrupt(DVP_CFG_START_INT_ENABLE | DVP_CFG_FINISH_INT_ENABLE, 1);
    }
    else
    {
        plic_irq_disable(IRQN_DVP_INTERRUPT);
        dvp_clear_interrupt(DVP_STS_FRAME_START | DVP_STS_FRAME_FINISH);
        dvp_config_interrupt(DVP_CFG_START_INT_ENABLE | DVP_CFG_FINISH_INT_ENABLE, 1);
    }

    return 0;
}

static void dvp_sccb_start_transfer(void)
{
    while (dvp->sts & DVP_STS_SCCB_EN)
        ;
    dvp->sts = DVP_STS_SCCB_EN | DVP_STS_SCCB_EN_WE;
    while (dvp->sts & DVP_STS_SCCB_EN)
        ;
}

static int _dvp_sccb_send_data(uint8_t dev_addr, uint16_t reg_addr, uint8_t reg_data, int g_sccb_reg_len)
{
    uint32_t tmp;

    tmp = dvp->sccb_cfg & (~DVP_SCCB_BYTE_NUM_MASK);

    (g_sccb_reg_len == 8) ? (tmp |= DVP_SCCB_BYTE_NUM_3) : (tmp |= DVP_SCCB_BYTE_NUM_4);

    dvp->sccb_cfg = tmp;

    if (g_sccb_reg_len == 8)
    {
        dvp->sccb_ctl = DVP_SCCB_WRITE_DATA_ENABLE | DVP_SCCB_DEVICE_ADDRESS(dev_addr) | DVP_SCCB_REG_ADDRESS(reg_addr) | DVP_SCCB_WDATA_BYTE0(reg_data);
    }
    else
    {
        dvp->sccb_ctl = DVP_SCCB_WRITE_DATA_ENABLE | DVP_SCCB_DEVICE_ADDRESS(dev_addr) | DVP_SCCB_REG_ADDRESS(reg_addr >> 8) | DVP_SCCB_WDATA_BYTE0(reg_addr & 0xff) | DVP_SCCB_WDATA_BYTE1(reg_data);
    }
    dvp_sccb_start_transfer();

    return 0;
}

static uint8_t _dvp_sccb_receive_data(uint8_t dev_addr, uint16_t reg_addr, int g_sccb_reg_len)
{
    uint32_t tmp;

    tmp = dvp->sccb_cfg & (~DVP_SCCB_BYTE_NUM_MASK);

    if (g_sccb_reg_len == 8)
        tmp |= DVP_SCCB_BYTE_NUM_2;
    else
        tmp |= DVP_SCCB_BYTE_NUM_3;

    dvp->sccb_cfg = tmp;

    if (g_sccb_reg_len == 8)
    {
        dvp->sccb_ctl = DVP_SCCB_WRITE_DATA_ENABLE | DVP_SCCB_DEVICE_ADDRESS(dev_addr) | DVP_SCCB_REG_ADDRESS(reg_addr);
    }
    else
    {
        dvp->sccb_ctl = DVP_SCCB_WRITE_DATA_ENABLE | DVP_SCCB_DEVICE_ADDRESS(dev_addr) | DVP_SCCB_REG_ADDRESS(reg_addr >> 8) | DVP_SCCB_WDATA_BYTE0(reg_addr & 0xff);
    }
    dvp_sccb_start_transfer();

    dvp->sccb_ctl = DVP_SCCB_DEVICE_ADDRESS(dev_addr);

    dvp_sccb_start_transfer();

    return (uint8_t)DVP_SCCB_RDATA_BYTE(dvp->sccb_cfg);
}

static int d_camif_init(rt_camif_t *ci)
{
    /* Init DVP IO map and function settings */
    fpioa_set_function(11, FUNC_CMOS_RST);
    fpioa_set_function(13, FUNC_CMOS_PWDN);
    fpioa_set_function(14, FUNC_CMOS_XCLK);
    fpioa_set_function(12, FUNC_CMOS_VSYNC);
    fpioa_set_function(17, FUNC_CMOS_HREF);
    fpioa_set_function(15, FUNC_CMOS_PCLK);
    fpioa_set_function(10, FUNC_SCCB_SCLK);
    fpioa_set_function(9, FUNC_SCCB_SDA);

    sysctl_set_spi0_dvp_data(1);

    dvp_init(16);

    dvp_set_xclk_rate(24000000);
    dvp_enable_burst();
    //dvp_set_output_enable(0, 1);
    dvp_set_output_enable(1, 1);
    dvp_set_image_format(DVP_CFG_RGB_FORMAT);

    plic_set_priority(IRQN_DVP_INTERRUPT, 1);
	plic_irq_register(IRQN_DVP_INTERRUPT, sensor_irq, ci->parent.user_data);

    return 0;
}

static int d_camif_reg_read(rt_camif_t *ci, uint8_t slv, uint16_t reg, int reglen, uint8_t *data)
{
    *data = _dvp_sccb_receive_data(slv, reg, reglen);

    return 0;
}

static int d_camif_reg_write(rt_camif_t *ci, uint8_t slv, uint16_t reg, int reglen, uint8_t *data)
{
    _dvp_sccb_send_data(slv, reg, *data, reglen);

    return 0;
}

static void d_camif_reset(rt_camif_t *ci, int level)
{
    if (level)
    {
        dvp->cmos_cfg |= DVP_CMOS_RESET;
    }
    else
    {
        dvp->cmos_cfg &= ~DVP_CMOS_RESET;
    }
}

static void d_camif_pwdn(rt_camif_t *ci, int level)
{
    if (level)
    {
        dvp->cmos_cfg |= DVP_CMOS_POWER_DOWN;
    }
    else
    {
        dvp->cmos_cfg &= ~DVP_CMOS_POWER_DOWN;
    }
}

static int d_camif_buf_active(rt_camif_t *ci, struct camif_buf *buf)
{
    dvp_set_display_addr((uint32_t)buf->data);

    return 0;
}

static int d_camif_stream_start(rt_camif_t *ci)
{
    sensor_run(1);

    return 0;
}

static int d_camif_set_format(rt_camif_t *ci, struct video_format *fmt)
{
    dvp_set_image_size(fmt->fmt.pix.width, fmt->fmt.pix.height);

    return 0;
}

static int d_camif_ioctl(rt_camif_t *ci, int cmd, long args)
{
    int ret = 0;

    switch (cmd)
    {
    case VIDIOC_SET_XCLK:
    {
        dvp_set_xclk_rate((uint32_t)args);
    }break;
    default:
    {
        ret = -1;
    }break;
    }

    return ret;
}

static struct camif_ops _ops =
{
    .init = d_camif_init,
    .sccb_read = d_camif_reg_read,
    .sccb_write = d_camif_reg_write,
    .reset = d_camif_reset,
    .pwdn = d_camif_pwdn,
    .buf_active = d_camif_buf_active,
    .stream_start = d_camif_stream_start,
    .set_format = d_camif_set_format,
    .ioctl = d_camif_ioctl,
};

struct d_camif _d_camif;

int k210_dvp_init(void)
{
    _d_camif.ci.ops = &_ops;

    rt_camif_register(&_d_camif.ci, "camif0", &_d_camif);

    return 0;
}
INIT_BOARD_EXPORT(k210_dvp_init);
